package cn.igroup.server.nio.server;

import cn.igroup.server.nio.conf.NioConfiguration;
import cn.igroup.server.nio.idle.AcceptorIdleStateTrigger;
import cn.igroup.server.nio.proto.Message;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.ImmediateEventExecutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;

/**
 * @author LONG
 * @date 2018/1/5 16:51
 */
@Service
public class NioServer {

    protected final Log logger = LogFactory.getLog(this.getClass());
    //private InetSocketAddress address;
    private static EventLoopGroup bossGroup;
    private static EventLoopGroup workerGroup;
    private final AcceptorIdleStateTrigger idleStateTrigger = new AcceptorIdleStateTrigger();
    private Channel channel;
    private final ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);

    @Autowired
    public NioConfiguration conf = new NioConfiguration();

    public ChannelFuture start() {
        final SslContext sslCtx;
        try {
            if (conf.useSecureSocketsLayer) {
                SelfSignedCertificate ssc = new SelfSignedCertificate();
                sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
            } else {
                sslCtx = null;
            }
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.option(ChannelOption.SO_BACKLOG, 512)
                    //默认心跳保活机制(2H)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //及时发送
                    .childOption(ChannelOption.TCP_NODELAY, true);
            //Netty4使用对象池，重用缓冲区
            //.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //ChannelOption.SO_RCVBUF  AND  ChannelOption.SO_SNDBUF 接收或者发送的系统缓冲区

            initGroups();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ChannelPipeline pipe = ch.pipeline();
                            if (sslCtx != null) {
                                pipe.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            pipe.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                            pipe.addLast(idleStateTrigger);

//                            pipe.addLast("decoder", new StringDecoder());
//                            pipe.addLast("frameEncoder", new LengthFieldPrepender(2));
//                            pipe.addLast("encoder", new StringEncoder());

                            pipe.addLast(new ProtobufVarint32FrameDecoder());
                            pipe.addLast(new ProtobufDecoder(Message.PushMessage.getDefaultInstance()));
                            pipe.addLast(new ProtobufVarint32LengthFieldPrepender());
                            pipe.addLast(new ProtobufEncoder());

                            pipe.addLast(new NioServerHandler());
                        }
                    });

            // Bind and start to accept incoming connections.
            ChannelFuture future = bootstrap.bind(conf.getHost(), conf.getPort()).sync();
            channel = future.channel();
            future.channel().closeFuture().sync();
            if (future.isSuccess()) {
                logger.debug("server start at port: " + conf.port + ", main work thread: " + Thread.currentThread().getId());
            }
            return future;
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
        } catch (SSLException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            stop();
        }
        return null;
    }

    public void stop() {
        if (channel != null) {
            channel.close();
        }
        channelGroup.close();
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().syncUninterruptibly();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully().syncUninterruptibly();
        }
        System.out.println("NioServer stopped");
    }

    protected void initGroups() {
        if (conf.useLinuxNativeEpoll) {
            bossGroup = new EpollEventLoopGroup(conf.getBossThreads());
            workerGroup = new EpollEventLoopGroup(conf.getWorkerThreads());
        } else {
            bossGroup = new NioEventLoopGroup(conf.getBossThreads());
            workerGroup = new NioEventLoopGroup(conf.getWorkerThreads());
        }
    }
}
